package cn.lijiajia3515.cairo.auth.config.security;

import cn.lijiajia3515.cairo.auth.framework.AccountRepository;
import cn.lijiajia3515.cairo.auth.framework.security.oauth2.OAuth2JwkProperties;
import cn.lijiajia3515.cairo.auth.framework.security.oauth2.resourceserver.jwt.authentication.CairoJwtAuthenticationConverter;
import cn.lijiajia3515.cairo.security.oauth2.resource.server.web.CairoBearerTokenAuthenticationEntryPoint;
import cn.lijiajia3515.cairo.security.oauth2.resource.server.web.CairoOAuth2HttpMessageAuthenticationFailedHandler;
import cn.lijiajia3515.cairo.security.web.authentication.CairoHttpMessageAccessDeniedHandler;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.JWSKeySelector;
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter;
import org.springframework.security.web.SecurityFilterChain;

import java.util.HashSet;
import java.util.Set;

/**
 * 资源服务器 配置
 */
@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter(JwtSetConfig.class)
public class ResourceServerConfig {

	/**
	 * resource server 优先
	 *
	 * @param http                        security
	 * @param jwtAuthenticationConverter  token 转换器
	 * @param authenticationEntryPoint    空认证处理
	 * @param authenticationFailedHandler 认证失败处理
	 * @param accessDeniedHandler         无权限处理
	 * @return resource 配置
	 * @throws Exception csrf异常
	 */
	@Bean
	@Order(100)
	SecurityFilterChain resourceServer(HttpSecurity http,
									   CairoJwtAuthenticationConverter jwtAuthenticationConverter,
									   CairoBearerTokenAuthenticationEntryPoint authenticationEntryPoint,
									   CairoOAuth2HttpMessageAuthenticationFailedHandler authenticationFailedHandler,
									   CairoHttpMessageAccessDeniedHandler accessDeniedHandler) throws Exception {
		http
			.antMatcher("/api/**")
			.csrf().disable()
			.cors().and()

			.authorizeRequests(c -> c
				.anyRequest().authenticated()
			)
			.oauth2ResourceServer(c -> c
				.jwt(jwt-> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter))
				.authenticationEntryPoint(authenticationEntryPoint)
				.withObjectPostProcessor(new ObjectPostProcessor<BearerTokenAuthenticationFilter>() {
					@Override
					public <O extends BearerTokenAuthenticationFilter> O postProcess(O object) {
						object.setAuthenticationFailureHandler(authenticationFailedHandler);
						return object;
					}
				}))

			.sessionManagement(c -> {
				c.sessionCreationPolicy(SessionCreationPolicy.STATELESS); // resource 资源 不使用cookie
			})

			.logout(AbstractHttpConfigurer::disable)

			.exceptionHandling(c -> {
				c.accessDeniedHandler(accessDeniedHandler)
					.authenticationEntryPoint(authenticationEntryPoint);
			});

		return http.build();
	}

	/**
	 * resource server 权限转换器
	 *
	 * @return 权限转换器
	 */
	@Bean
	public JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter() {
		JwtGrantedAuthoritiesConverter bean = new JwtGrantedAuthoritiesConverter();
		bean.setAuthorityPrefix("scp_");
		return bean;
	}

	/**
	 * oauth2 resource server token 转换器
	 *
	 * @param accountRepository    account仓库
	 * @param authoritiesConverter 权限转换器
	 * @return token转换器
	 */
	@Bean
	@Primary
	public CairoJwtAuthenticationConverter jwtAuthenticationConverter(AccountRepository accountRepository, JwtGrantedAuthoritiesConverter authoritiesConverter) {
		CairoJwtAuthenticationConverter bean = new CairoJwtAuthenticationConverter(accountRepository);
		bean.setGrantedAuthoritiesConverter(authoritiesConverter);
		return bean;
	}

	// /**
	//  * jwt 解码器
	//  * @param jwk jwk
	//  * @return
	//  * @throws JOSEException
	//  */
	// @Bean
	// @ConditionalOnMissingBean
	// NimbusJwtDecoder configJwkDecoder(JWK jwk) throws JOSEException {
	// 	if (jwk instanceof RSAPublicKey) {
	// 		return NimbusJwtDecoder.withPublicKey(((RSAPublicKey) jwk)).build();
	// 	} else if (jwk instanceof RSAKey) {
	// 		return NimbusJwtDecoder.withPublicKey(((RSAKey) jwk).toRSAPublicKey()).build();
	// 	}
	//
	// 	throw new RuntimeException("无法配置 jwtDecoder");
	// }


	/**
	 * jwt 解码器
	 *
	 * @param jwkSource 签名context
	 * @return jwt 解码器
	 * @see JwtSetConfig#cairoJwkSource(OAuth2JwkProperties) (JWK) 使用当前应用的jwt资源
	 */
	@Bean
	JwtDecoder cairoJwtDecoder(JWKSource<SecurityContext> jwkSource) {
		OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
		Set<JWSAlgorithm> jwsAlgs = new HashSet<>();
		jwsAlgs.addAll(JWSAlgorithm.Family.RSA);
		jwsAlgs.addAll(JWSAlgorithm.Family.EC);
		jwsAlgs.addAll(JWSAlgorithm.Family.HMAC_SHA);
		ConfigurableJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
		JWSKeySelector<SecurityContext> jwsKeySelector =
			new JWSVerificationKeySelector<>(jwsAlgs, jwkSource);
		jwtProcessor.setJWSKeySelector(jwsKeySelector);
		// Override the default Nimbus claims set verifier as NimbusJwtDecoder handles it instead
		jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {
		});
		return new NimbusJwtDecoder(jwtProcessor);
	}

}
